package org.jfrog.jade.plugins.common.injectable;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.tools.ant.Project;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.jfrog.jade.plugins.common.ant.Maven2AntManager;
import org.jfrog.jade.plugins.common.ant.Maven2AntManagerDefaultImpl;
import org.jfrog.jade.plugins.common.naming.ProjectNameProvider;
import org.jfrog.jade.plugins.common.uptodate.UpToDateManager;
import org.jfrog.jade.plugins.common.uptodate.UpToDateManagerImpl;
import org.jfrog.maven.annomojo.annotations.MojoComponent;
import org.jfrog.maven.annomojo.annotations.MojoParameter;

import java.io.File;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by IntelliJ IDEA.
 * User: yoavl
 */
public abstract class MvnInjectableSupport implements MvnInjectable {

    @MojoComponent(description = "project-helper instance, used to make addition of resources simpler")
    private MavenProjectHelper projectHelper;

    @MojoComponent(description = "Used to look up Artifacts in the local or remote repository.")
    private ArtifactResolver artifactResolver;

    @MojoComponent(description = "Used to look up Artifacts in the local or remote repository.")
    private ArtifactFactory artifactFactory;

    @MojoParameter(expression = "${project}", required = true, readonly = true,
            description = "The Maven Project")
    private MavenProject project;

    @MojoParameter(expression = "${localRepository}",
            description = "The local repository")
    private ArtifactRepository localRepository;


    @MojoParameter(expression = "${plugin.artifacts}",
            description = "The plugin dependency artifacts")
    private List<Artifact> pluginArtifacts;


    private Log log;

   @MojoComponent(roleHint = "ant",
            description = "The manager to link maven project with an Ant project.")
    private Maven2AntManager maven2AntManager;

    @MojoComponent(roleHint = "name")
    private ProjectNameProvider nameProvider;

    @MojoComponent
    private UpToDateManager upToDateManager;

    public MvnInjectableSupport() {
    }

    public MavenProject getProject() {
        return project;
    }

    public void setProject(MavenProject project) {
        this.project = project;
    }

    public MavenProjectHelper getProjectHelper() {
        return projectHelper;
    }

    public void setProjectHelper(MavenProjectHelper projectHelper) {
        this.projectHelper = projectHelper;
    }

    public Log getLog() {
        return log;
    }

    public void setLog(Log log) {
        this.log = log;
    }

    public ArtifactRepository getLocalRepository() {
        return localRepository;
    }

    public void setLocalRepository(ArtifactRepository localRepository) {
        this.localRepository = localRepository;
    }

    public ArtifactResolver getArtifactResolver() {
        return artifactResolver;
    }

    public void setArtifactResolver(ArtifactResolver artifactResolver) {
        this.artifactResolver = artifactResolver;
    }

    public ArtifactFactory getArtifactFactory() {
        return artifactFactory;
    }

    public void setArtifactFactory(ArtifactFactory artifactFactory) {
        this.artifactFactory = artifactFactory;
    }

   public Maven2AntManager getMaven2AntManager() {
        if (maven2AntManager == null) {
            maven2AntManager = new Maven2AntManagerDefaultImpl();
        }
        return maven2AntManager;
    }

    public void setMaven2AntManager(Maven2AntManager maven2AntManager) {
        this.maven2AntManager = maven2AntManager;
    }

    public ProjectNameProvider getNameProvider() {
        return nameProvider;
    }

    public void setNameProvider(ProjectNameProvider nameProvider) {
        this.nameProvider = nameProvider;
    }



    public UpToDateManager getUpToDateManager() {
        if (upToDateManager == null) {
            upToDateManager = new UpToDateManagerImpl();
        }
        return upToDateManager;
    }


   public void setUpToDateManager(UpToDateManager upToDateManager) {
        this.upToDateManager = upToDateManager;
    }

    public List<Artifact> getPluginArtifacts() {
        return pluginArtifacts;
    }

    public void setPluginArtifacts(List<Artifact> pluginArtifacts) {
        this.pluginArtifacts = pluginArtifacts;
    }

    public void updateFromMvnInjectable(MvnInjectable injectable) {
        //Set the properties that we expect has been injected to the mojo
        setProject(injectable.getProject());
        setProjectHelper(injectable.getProjectHelper());
        setLog(injectable.getLog());
        setArtifactFactory(injectable.getArtifactFactory());
        setArtifactResolver(injectable.getArtifactResolver());
        setLocalRepository(injectable.getLocalRepository());
        setMaven2AntManager(injectable.getMaven2AntManager());
        setNameProvider(injectable.getNameProvider());
        setPluginArtifacts(injectable.getPluginArtifacts());
    }


    public Project getAntProject() {
        return getMaven2AntManager().getAntProject(this);
    }


    public void resolveArtifacts(Collection<Artifact> artifacts) throws ArtifactNotFoundException, ArtifactResolutionException {
        for (Artifact artifact : artifacts) {
            getArtifactResolver().resolve(artifact, getProject().getRemoteArtifactRepositories(),
                    getLocalRepository());
        }
    }

 /*   public Path resolveAndGetAntPath(Collection<Artifact> artifacts) throws ArtifactNotFoundException, ArtifactResolutionException {
        Project antProject = getAntProject();
        Path path = new Path(antProject);
        resolveArtifacts(artifacts);
        getMaven2AntManager().fillPathFromArtifacts(path, artifacts);
        return path;
    }*/

    public void createFolder(File destDir) throws MojoExecutionException {
        if (!destDir.exists()) {
            getLog().info("Creating folder " + destDir);
            if (!destDir.mkdirs()) {
                throw new MojoExecutionException("Cannot create directory "
                        + destDir);
            }
        } else {
            if (!destDir.isDirectory()) {
                throw new MojoExecutionException("Path "
                        + destDir + " is not a directory");
            }
        }
    }

    protected void fillPluginSettings(List<Plugin> buildPlugins, String pluginArtifactId, Object toFill, Class pluginClass) {
        if (toFill == null) {
            throw new RuntimeException("Cannot fill data to empty class");
        }
        if (pluginClass == null) {
            pluginClass = toFill.getClass();
        }

        // Frist get the map of simple configuration entry
        Map<String, String> optionNameValues = new HashMap<String, String>();
        for (Plugin plugin : buildPlugins) {
            if (plugin.getArtifactId().equals(pluginArtifactId)) {
                Xpp3Dom o = (Xpp3Dom) plugin.getConfiguration();
                if (o != null) {
                    Xpp3Dom[] doms = o.getChildren();
                    for (int i = 0; i < doms.length; i++) {
                        Xpp3Dom dom = doms[i];
                        // Take only element with no children, meaning simple ones
                        if (dom.getChildCount() == 0) {
                            optionNameValues.put(dom.getName(), dom.getValue());
                        }
                    }
                }
                break;
            }
        }

        fillDeclaredFields(pluginClass, toFill, optionNameValues);

        // TODO: do the setter/getter thing
        /*
        Method[] declaredMethods = pluginClass.getDeclaredMethods();
        for (int i = 0; i < declaredMethods.length; i++) {
            Method declaredMethod = declaredMethods[i];

        }
        */
    }

    private void fillDeclaredFields(Class pluginClass, Object toFill, Map<String, String> optionNameValues) {
        Field[] declaredFields = pluginClass.getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Field declaredField = declaredFields[i];
            String fieldName = declaredField.getName();
            if (declaredField.isAnnotationPresent(MojoParameter.class)) {
                String value = optionNameValues.get(fieldName);
                // Use the defaultValue from annotation if null
                if (value == null) {
                    MojoParameter mojoParamAnno = declaredField.getAnnotation(MojoParameter.class);
                    value = mojoParamAnno.defaultValue();
                    if (value != null && value.trim().length() == 0) {
                        value = null;
                    }
                }
                if (value != null) {
                    declaredField.setAccessible(true);
                    try {
                        // TODO: Do the type Convertion using the OGNL converter
                        Class type = declaredField.getType();
                        if (type == int.class || type == Integer.class) {
                            declaredField.set(toFill, new Integer(value));
                        } else if (type == boolean.class || type == Boolean.class) {
                            declaredField.set(toFill, Boolean.valueOf(value));
                        } else if (type == String.class) {
                            declaredField.set(toFill, value);
                        } else {
                            getLog().warn("MojoParameter type " + type + " in field " + declaredField + " not supported on Anno Mojo Fill");
                        }
                    } catch (IllegalAccessException e) {
                        getLog().error(e);
                    }
                }
            }
        }
        Class superclass = pluginClass.getSuperclass();
        if (superclass != Object.class) {
            fillDeclaredFields(superclass, toFill, optionNameValues);
        }
    }

    protected String getFailSafeScope(Artifact artifact) {
        String scope = artifact.getScope();
        if (scope == null || scope.length() == 0)
            scope = Artifact.SCOPE_RUNTIME;
        return scope;
    }
}
